<!doctype html>
<html lang="zh-CN">
<head>
  <meta charset="utf-8" />
  <title>Kexio 权限体系·全集成流程图（登录/鉴权/登出/事件/投影）</title>
  <meta name="viewport" content="width=device-width,initial-scale=1" />
  <style>
    :root { --bg:#f7f9fc; --fg:#0f172a; --muted:#64748b; --accent:#2563eb; --ok:#16a34a; --warn:#ea580c; --err:#dc2626; --box:#ffffff; --line:#cbd5e1; }
    body { margin:0; font-family:system-ui,-apple-system,Segoe UI,Roboto,Inter,Arial; background:var(--bg); color:var(--fg); }
    .wrap { max-width:1220px; margin:24px auto; padding:0 16px; }
    h1 { font-size:20px; margin:0 0 16px; }
    .board { display:grid; grid-template-columns: repeat(5, minmax(240px, 1fr)); gap:16px; align-items:start; }
    @media (max-width: 1280px) { .board { grid-template-columns: repeat(2, minmax(300px, 1fr)); } }
    @media (max-width: 720px) { .board { grid-template-columns: repeat(1, minmax(300px, 1fr)); } }
    .lane-title { font-size:13px; color:var(--muted); padding:0 4px 4px; }
    .col { display:flex; flex-direction:column; gap:10px; }
    .box { background:var(--box); border:1px solid var(--line); border-radius:8px; padding:10px 12px; box-shadow:0 1px 2px rgba(0,0,0,.04); font-size:13px; }
    .box .hd { font-weight:600; margin-bottom:6px; }
    .box .cls { color:var(--accent); font-weight:600; }
    .box .tx { color:var(--muted); line-height:1.35; }
    .ok { border-left:4px solid var(--ok); }
    .warn { border-left:4px solid var(--warn); }
    .err { border-left:4px solid var(--err); }
    .dotted { border-style:dashed; opacity:.9; }
    .arrow-v { display:flex; align-items:center; justify-content:center; height:18px; }
    .arrow-v::after { content:"↓"; color:var(--muted); font-size:18px; }
    code { background:#eef2ff; color:#1e293b; padding:1px 6px; border-radius:6px; }
    .legend { margin-top:18px; display:flex; gap:12px; flex-wrap:wrap; font-size:12px; color:var(--muted); }
    .pill { padding:2px 8px; border-radius:999px; border:1px solid var(--line); background:#fff; }
  </style>
</head>
<body>
<div class="wrap">
  <div class="nav"><a class="active" href="权限总览.html">总览</a> <a href="模块1-应用启动模块.html">模块1</a> <a href="模块2-通用工具模块.html">模块2</a> <a href="模块3-认证授权模块.html">模块3</a> <a href="模块4-用户管理模块.html">模块4</a></div>
  <h1>Kexio 权限体系 · 全集成流程图（涵盖模块：应用启动/认证授权/用户管理/事件与投影）</h1>
  <style>
    .nav{display:flex;gap:8px;flex-wrap:wrap;margin:0 0 12px}
    .nav a{padding:4px 10px;border:1px solid var(--line);border-radius:999px;background:#fff;color:var(--accent);text-decoration:none}
    .nav a.active{background:#eef2ff}
  </style>

  <div class="board">
    <!-- 列1：客户端/控制器入口 -->
    <div>
      <div class="lane-title">入口（客户端/控制器）</div>
      <div class="col">
        <div class="box ok">
          <div class="hd">登录</div>
          <div class="tx">POST <code>/api/auth/login</code><br/>控制器：<span class="cls">com.kexio.user.controller.AuthController</span></div>
        </div>
        <div class="arrow-v"></div>
        <div class="box warn">
          <div class="hd">受保护接口</div>
          <div class="tx">GET/POST <code>/api/**</code>（非白名单）<br/>配置：<code>kexio.auth.web.whitelist</code> & <code>@PublicApi</code></div>
        </div>
        <div class="arrow-v"></div>
        <div class="box">
          <div class="hd">登出</div>
          <div class="tx">POST <code>/api/auth/logout</code><br/>控制器：<span class="cls">AuthController</span></div>
        </div>
      </div>
    </div>

    <!-- 列2：安全网关/白名单/过滤器 -->
    <div>
      <div class="lane-title">安全入口（白名单/过滤器）</div>
      <div class="col">
        <div class="box">
          <div class="hd">白名单判定</div>
          <div class="tx"><span class="cls">WebSecurityConfig</span> / <span class="cls">PublicApiSecurityConfigurer</span><br/>
            分支：
            <ul class="tx" style="margin:6px 0 0 14px; padding:0">
              <li>命中白名单 → 放行</li>
              <li>未命中 → 进入 JWT 认证过滤</li>
            </ul>
          </div>
        </div>
        <div class="arrow-v"></div>
        <div class="box">
          <div class="hd">JWT 认证过滤</div>
          <div class="tx"><span class="cls">com.kexio.auth.filter.JwtAuthenticationFilter</span><br/>
            - 提取/校验 Token（<span class="cls">JwtProvider</span>）<br/>
            - 获取版本（<span class="cls">PermissionVersionService</span>）
          </div>
        </div>
        <div class="arrow-v"></div>
        <div class="box dotted">
          <div class="hd">缓存/画像装配（分支）</div>
          <div class="tx">
            A. 缓存命中 → 直接组装 <code>UserAuthInfo</code><br/>
            B. 未命中 → <span class="cls">UserAuthInfoProvider</span>（默认 Redis 画像）加载
          </div>
        </div>
      </div>
    </div>

    <!-- 列3：认证授权模块（读侧） -->
    <div>
      <div class="lane-title">认证授权（读侧）</div>
      <div class="col">
        <div class="box">
          <div class="hd">多级缓存</div>
          <div class="tx"><span class="cls">MultiLevelCacheService</span><br/>L1：Caffeine（本地） / L2：Redis（分布式）</div>
        </div>
        <div class="arrow-v"></div>
        <div class="box">
          <div class="hd">只读画像 Provider</div>
          <div class="tx"><span class="cls">RedisUserAuthInfoProvider</span><br/>从 Redis Set 读取 <code>roles/perms</code> 与版本</div>
        </div>
        <div class="arrow-v"></div>
        <div class="box">
          <div class="hd">方法级权限 + 数据范围</div>
          <div class="tx"><span class="cls">PermissionAspect</span> → <span class="cls">PermissionEvaluator</span><br/>
            （可选）<span class="cls">DataScopeAspect</span> 拼接 WHERE
          </div>
        </div>
      </div>
    </div>

    <!-- 列4：用户管理模块（写侧） -->
    <div>
      <div class="lane-title">用户管理（写侧）</div>
      <div class="col">
        <div class="box">
          <div class="hd">用户-角色变更</div>
          <div class="tx"><span class="cls">UserServiceImpl.assign/remove/syncRoles</span><br/>
            - 写画像：<span class="cls">PermissionProjectionWriter</span><br/>
            - 版本：<span class="cls">PermissionVersionService.increment*</span><br/>
            - 事件：<span class="cls">PermissionChangeNotifier.userPermissionChanged</span>
          </div>
        </div>
        <div class="arrow-v"></div>
        <div class="box">
          <div class="hd">角色权限变更</div>
          <div class="tx"><span class="cls">RoleServiceImpl.assign/remove/syncPermissions</span><br/>
            - 查询受影响用户 → 批量写画像（pipeline）<br/>
            - 批量版本自增 + 批量事件
          </div>
        </div>
        <div class="arrow-v"></div>
        <div class="box">
          <div class="hd">登录预热</div>
          <div class="tx"><span class="cls">AuthServiceImpl.login</span> → 读取 <code>roles/perms</code> → 预热画像 + 版本自增 + 事件</div>
        </div>
      </div>
    </div>

    <!-- 列5：事件与版本/订阅器 -->
    <div>
      <div class="lane-title">事件 / 版本 / 订阅器</div>
      <div class="col">
        <div class="box">
          <div class="hd">权限版本仓储</div>
          <div class="tx"><span class="cls">PermissionVersionRepository</span><br/>默认：Redis 版本键；回退：DB（条件装配）</div>
        </div>
        <div class="arrow-v"></div>
        <div class="box ok">
          <div class="hd">发布器（Redis Streams）</div>
          <div class="tx"><span class="cls">RedisStreamsPermissionChangeNotifier</span><br/>
            配置：<code>kexio.auth.events.publisher=redis</code><br/>
            事件体：<code>type/tenantId/userId/userIds/reason/ts</code>
          </div>
        </div>
        <div class="arrow-v"></div>
        <div class="box ok">
          <div class="hd">订阅器（消费组）</div>
          <div class="tx"><span class="cls">RedisStreamsPermissionChangeSubscriber</span><br/>
            - <code>lastConsumed</code> 连续消费 + <code>ack</code><br/>
            - 定向失效 L1：<code>PermissionVersionService.invalidate</code><br/>
            - 未指定用户：<code>clearVersionCache(tenantId)</code>
          </div>
        </div>
      </div>
    </div>
  </div>

  <div class="legend">
    <span class="pill">配置开关：<code>kexio.auth.redis-projection.enabled=true</code>（读侧画像）</span>
    <span class="pill">事件总线：<code>kexio.auth.events.enabled=true</code>，<code>publisher=redis</code></span>
    <span class="pill">Prometheus：<code>/actuator/prometheus</code></span>
  </div>
</div>
</body>
</html>

